package com.dcivision.lucene;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.LogFactoryImpl;

import com.dcivision.dms.bean.DmsRelationalWord;
import com.dcivision.dms.dao.DmsRelationalWordDAObject;
import com.dcivision.framework.SessionContainer;
import com.dcivision.framework.SystemParameterConstant;
import com.dcivision.framework.SystemParameterFactory;
import com.dcivision.framework.TextUtility;
import com.dcivision.framework.Utility;

/**
 * Parse keyword from relation database keyword or GB-BIG5/BIG5-GB relation word.
 * @author Administrator
 *
 */
public class IndexKeywordParser {
  
  private Log log = new LogFactoryImpl().getInstance(this.getClass());
  
  private Connection conn = null;
  
  private SessionContainer sessionContainer = null;
  
  private String fullKeywords = null;

  private List lstKeywords = null;
  
  private DmsRelationalWordDAObject dmsRelationalWordDAO = null;
  
  public IndexKeywordParser(SessionContainer sessionContainer, Connection conn) {
    this.conn = conn;
    this.sessionContainer = sessionContainer;
    this.lstKeywords = new ArrayList();
    this.dmsRelationalWordDAO = new DmsRelationalWordDAObject(this.sessionContainer, this.conn);
  }
  
  /**
   * Parse keyword, get its' setted relation word or GB<->GBK relation word.
   * @param keyword
   * @throws Exception
   */
  public void parse(String skeyword) throws Exception {
    
    // covert symbol to space, all symbol will be ignore at search, except '|' '&' '\"'.
    StringBuffer sbKeyword = new StringBuffer();
    for (int i=0; i<skeyword.length(); i++) {
      char ch = skeyword.charAt(i);
      if (this.isSymbol(ch) && "\"|&".indexOf(ch)==-1) {
        sbKeyword.append(' ');
      }else {
        sbKeyword.append(ch);
      }
    }
    
    String keyword = sbKeyword.toString();

    StringBuffer sbNewKeyword = new StringBuffer();
    int st = 0;
    boolean canSplit = false;
    boolean isEnglishWord = true;
    for (int i=0; i<keyword.length(); i++) {
      char ch = keyword.charAt(i);
      char chp = keyword.charAt(i==0?i:(i-1));
      if ("\"".indexOf(ch)!=-1 && chp!='\\') {
        if (canSplit) {
          String subKeyword = keyword.substring(st, i);
          sbNewKeyword.append(this.convert(subKeyword, false, isEnglishWord));
          isEnglishWord = true;
        }
        int idx_close_quot = 0;
        while (true) {
          idx_close_quot = keyword.indexOf("\"", i+1);
          if (idx_close_quot==-1) {
            idx_close_quot = keyword.length();
            break;
          }else {
            char chPrev = keyword.charAt(i==0?i:(i-1));
            if (chPrev!='\\') {
              break;
            }
          } 
        }
        String subKeyword = keyword.substring(i+1, idx_close_quot);
        sbNewKeyword.append(this.convert(subKeyword, true, false));
        isEnglishWord = true;
        
        //i  = idx_close_quot+1;
        //st = idx_close_quot+1;
        i  = idx_close_quot;
        st = idx_close_quot;
        canSplit = false;
      }else if (' '==ch) {
        if (canSplit) {
          String subKeyword = keyword.substring(st, i);
          sbNewKeyword.append(this.convert(subKeyword, false, isEnglishWord));
          isEnglishWord = true;
        }
        while ((i+1)<keyword.length() && ' '==keyword.charAt(i+1)) {
          i++;
        }
        if ((i+4)<keyword.length() && keyword.substring(i, i+4).toLowerCase().equals(" || ")) {
          sbNewKeyword.append(" OR ");
          i = i+3;
          st = i+3;
        }else if ((i+3)<keyword.length() && keyword.substring(i, i+3).toLowerCase().equals(" | ")) {
          sbNewKeyword.append(" OR ");
          i = i+2;
          st = i+2;
        }else if ((i+4)<keyword.length() && keyword.substring(i, i+4).toLowerCase().equals(" && ")) {
          sbNewKeyword.append(" AND ");
          i = i+3;
          st = i+3;
        }else if ((i+3)<keyword.length() && keyword.substring(i, i+3).toLowerCase().equals(" & ")) {
          sbNewKeyword.append(" AND ");
          i = i+2;
          st = i+2;
        }else if ((i+4)<keyword.length() && keyword.substring(i, i+4).toLowerCase().equals(" or ")) {
          sbNewKeyword.append(" OR ");
          i = i+3;
          st = i+3;
        }else if ((i+5)<keyword.length() && keyword.substring(i, i+5).toLowerCase().equals(" and ")) {
          sbNewKeyword.append(" AND ");
          i = i+4;
          st = i+4;
        }else if ((i+1)<keyword.length()) {
          sbNewKeyword.append(" AND ");
        }
        while ((i+1)<keyword.length() && ' '==keyword.charAt(i+1)) {
          i++;
        }
        st = i+1;
        canSplit = false;
      }else {
        if (!this.isLetter(ch) && !this.isNumber(ch)) {
          isEnglishWord = false;
        }
        canSplit = true;
      }
    }
    if (canSplit) {
      String subKeyword = keyword.substring(st);
      sbNewKeyword.append(this.convert(subKeyword, false, isEnglishWord));
    }
    this.fullKeywords = sbNewKeyword.toString();
  }
  
  /**
   * Get parsed result.
   * @return
   */
  public String getParseKeywordString() {
    return this.fullKeywords;
  }
  
  /**
   * Get parsed keyword.
   * @return
   */
  public List getParseKeywordList() {
    return this.lstKeywords;
  }
  
  /**
   * Search and add relation keyword from database and convert to query expression.
   * @param subKeyword
   * @param withQuot
   * @return
   * @throws Exception
   */
  private String convert(String subKeyword, boolean withQuot, boolean isEnglishWord) throws Exception {
    boolean enableRelationWord = SystemParameterFactory.getSystemParameterBoolean(SystemParameterConstant.DMS_RELATIONAL_WORD_ENABLE);
    // note: this parameter (DMS_INDEX_SEARCH_EXACT_MATCH) is disabled, program is not completed, only prepared for future.
    boolean searchExactMatch = SystemParameterFactory.getSystemParameterBoolean(SystemParameterConstant.DMS_INDEX_SEARCH_EXACT_MATCH);
    
    String suffix = (searchExactMatch || !isEnglishWord) ? "" : "*";
    
    List lstSubKeywords = this.searchKeywords(subKeyword, enableRelationWord);
    this.lstKeywords.addAll(lstSubKeywords);
    
    StringBuffer sbNewKeyword = new StringBuffer();
    
    sbNewKeyword.append(" (");
    if (lstSubKeywords.size()>1) {
      for (int i=0; i<lstSubKeywords.size(); i++) {
        sbNewKeyword.append(i!=0 ? " " : "");
        sbNewKeyword.append(withQuot ? "\""+lstSubKeywords.get(i)+"\"" : (lstSubKeywords.get(i)+suffix));
      }
    }else {
      sbNewKeyword.append(withQuot ? "\""+lstSubKeywords.get(0)+"\"" : lstSubKeywords.get(0)+suffix);
    }
    sbNewKeyword.append(")");
    return sbNewKeyword.toString();
  }
    
  /**
   * split words with special character.
   * example: 'dms document' will be split to 'dms' and 'document'
   * @param lstSubKeywords
   * @return
   * @throws Exception
   */
  private List extractFromSpecialCharacter(List lstSubKeywords) throws Exception {
    List lstNewSubKeywords = new ArrayList();
    String specialChar = " ";//"\\+-!():^]{}~*?";
    for (int i=0; i<lstSubKeywords.size(); i++) {
      String subKeyword = (String)lstSubKeywords.get(i);
      lstNewSubKeywords.add(subKeyword);
      int st = 0;
      for (int idx=0; idx<subKeyword.length(); idx++) {
        char ch = subKeyword.charAt(idx);
        if (specialChar.indexOf(ch)!=-1) {
          int index = subKeyword.indexOf(ch);
          if (index!=st) {
            lstNewSubKeywords.add(subKeyword.substring(st, index));
            st = index+1;
          }
        }
      }
      if (st!=0 && st!=subKeyword.length()) {
        lstNewSubKeywords.add(subKeyword.substring(st));
      }
    }
    return lstNewSubKeywords;
  }
  
  /**
   * @param subKeyword
   * @return
   * @throws Exception
   * @deprecated
   */
  private String escapeSpecialCharacter(String subKeyword) throws Exception {
    String newKeyword = "";
    String stopword = "\\+-!():^]{}~*?";
    for (int i=0; i<subKeyword.length(); i++) {
      char ch = subKeyword.charAt(i);
      if (stopword.indexOf(ch)!=-1) {
        newKeyword += "\\"+ch;
      }else {
        newKeyword += ch;
      }
    }
    return newKeyword;
  }
  
  /**
   * Search relation keyword from database
   * @param subKeyword
   * @param enableRelationWord
   * @return
   * @throws Exception
   */
  private List searchKeywords(String subKeyword, boolean enableRelationWord) throws Exception {
    List lstKeywords = new ArrayList();
    lstKeywords.add(subKeyword.toLowerCase());
    if (enableRelationWord) {
      List relationalWordList = this.dmsRelationalWordDAO.getRelationalWordListByKeyWord(subKeyword);
      for (int n=0; n<relationalWordList.size(); n++) {
        DmsRelationalWord relationWord = (DmsRelationalWord)relationalWordList.get(n);
        String relationKeyword = relationWord.getWord();
        if (!Utility.isEmpty(relationKeyword) && !lstKeywords.contains(relationKeyword.toLowerCase())) {
          lstKeywords.add(relationKeyword.toLowerCase());
        }
      }
    }
    String subKeywordConvert = TextUtility.convert(subKeyword);
    if (!subKeywordConvert.equals(subKeyword)) {
      lstKeywords.add(subKeywordConvert.toLowerCase());
      if (enableRelationWord) {
        List relationalWordListConvert = this.dmsRelationalWordDAO.getRelationalWordListByKeyWord(subKeywordConvert);
        for (int m=0; m<relationalWordListConvert.size(); m++) {
          DmsRelationalWord relationWord = (DmsRelationalWord)relationalWordListConvert.get(m);
          String relationKeyword = relationWord.getWord();
          if (Utility.isEmpty(relationKeyword) && !lstKeywords.contains(relationKeyword.toLowerCase())) {
            lstKeywords.add(((String)relationalWordListConvert.get(m)).toLowerCase());
          }
        }
      }
    }
    return lstKeywords;
  }
  
  private boolean isLetter(char ch) {
    if ((ch>=65 && ch<=90) || (ch>=97 && ch<=122)) {
      return true;
    }else {
      return false;
    }
  }
  
  private boolean isNumber(char ch) {
    if (ch>=48 && ch<=57) {
      return true;
    }else {
      return false;
    }
  }
  
  private boolean isSymbol(char ch) {
    if (ch<127 && !this.isNumber(ch) && !this.isLetter(ch)) {
      return true;
    }else {
      return false;
    }
  }
}
